@use 'sass:string';
@use 'sass:list';
@use 'sass:map';
@use 'sass:math';
@use 'sass:meta';
@use 'legacy';
@use 'sass-true/sass/config/throw';
@use 'sass-true/sass/true' as *;

/*
  Functions
  TODO Refactor to different categories
*/

$DELIMETER: '-';

/*
    Converts a given hex value to RGB.
*/
@function hex-to-rgb($hex) {
    @return legacy.red($hex), legacy.green($hex), legacy.blue($hex);
}

@function to-hover-color($color, $amount) {
    $brightness: calculate-brightness($color);

    // Adjust color based on brightness
    $adjusted-color: $color;
    @if ($brightness < 128) {
      // Lighten the color
      $adjusted-color: legacy.lighten($color, $amount);
    } @else {
      // Darken the color
      $adjusted-color: legacy.darken($color, $amount);
    }
  
    // Output the adjusted color
    @return $adjusted-color;
}

/*
    Calculate brightness based on relative luminance formula
    https://www.w3.org/WAI/GL/wiki/Relative_luminance#:~:text=Definition%20as%20Stated%20in%20WCAG%202.,-x&text=Note%201%3A%20For%20the%20sRGB,%2B0.055)%2F1.055)%20%5E%202.4
*/
@function calculate-brightness($color) {
    // Extract RGB components
    $red: legacy.red($color);
    $green: legacy.green($color);
    $blue: legacy.blue($color);

    // Calculate brightness using the relative luminance formula
    @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue;
}

/*
    Generates delimited class name prefix.
*/
@function delimitize($name) {
    @return $DELIMETER + $name + $DELIMETER;
}

@function string-split($string, $delimiter) {
    $result: ();

    // Find first occurence of delimiter
    $index: string.index($string, $delimiter);

    // Loop to parse string
    @while $index != null {
        // Get substring left of delimiter
        $substring: string.slice($string, 1, $index - 1);
        $result: append($result, $substring);

        // Remove delimiter from string
        $string: string.slice($string, $index + string.length($delimiter));

        // Find next delimiter
        $index: string.index($string, $delimiter);
    }

    // Add last substring to the result
    $result: append($result, $string);
    @return $result;
}

@function map-get-deep($map, $query) {
    $query-list: string-split($query, '.');

    $result: $map;
    @each $query in $query-list {
        $result: map-get-strict($result, $query);
    }

    @return $result;
}

@function map-get-strict($map, $key) {
    @if map.has-key($map, $key) {
        @return map.get($map, $key);
    } @else {
        @return throw.error(
            $message: 'ERROR: Specified key "#{$key}" does not exist in the mapping',
            $source: 'map-get-strict()',
            $catch: true
        );
    }
}

/*
    Gets entry from given config by key and will merge with extended properties inside the extend map. This function also exists because merging with a null map does not work.
    Use case is that if we have somthing like this:

    $config: (
        opacity: null, // Disable default opacity classes
        extends: (
            // Framework will now extend with your config and generate .u-opacity-42
            opacity: (
                42: .42
            )
        )
    );
*/
@function get-with-extend($all-config, $keys...) {
    $result: ();

    @if map.get($all-config, $keys...) != null {
        $result: map.deep-merge($result, map.get($all-config, $keys...));
    }

    @if map.get($all-config, extend, $keys...) != null {
        $result: map.deep-merge($result, map.get($all-config, extend, $keys...));
    }

    @return $result;
}

/*
    Converts a list of properties to a mapping where the key and value are the property
    itself.
*/
@function to-property-map($properties) {
    @if meta.type-of($properties) == map {
        @error "Input is already a map."
    }

    $result: ();

    @each $property in $properties {
        $result: map.set($result, $property, $property);
    }

    @return $result;
}

/*
    To generate the negative variant of classes, we want to prepend 'n' to the key and add a negative sign to the value.

    This automatically skips string values and 0 values.
    This also assumes that the values passed in are positive variants of classes. This will break with negative variants.
*/
@function get-negative-value-map($map) {
    $result: ();
    @each $key, $value in $map {
        $to-add: (
            $key: $value,
        );

        @if meta.type-of($value) == number and $key != 0 and strip-unit($value) != 0 {
            $to-add: map.merge($to-add, (n#{$key}: -#{$value}));
        }

        $result: map.merge($result, $to-add);
    }
    @return $result;
}

/// Remove the unit of a length
/// @param {Number} $number - Number to remove unit from
/// @return {Number} - Unitless number
@function strip-unit($number) {
    @if meta.type-of($number) == 'number' and not math.is-unitless($number) {
        @return math.div($number, ($number * 0 + 1));
    }

    @return $number;
}

/*
    Merge multiple maps with each other.

    $merged: map-multi-merge($map1, $map2, $map3, ...);
*/
@function map-multi-merge($maps...) {
    $result: ();

    @each $map in $maps {
        $result: map.merge($result, $map);
    }

    @return $result;
}

/*
    Filters over a given map only for keys between $start inclusive and
    $end exclusive.
*/
@function map-range($map, $start, $end, $include-non-numeric: false) {
    $result: ();
    @each $key, $value in $map {
        @if meta.type-of($key) == 'number' {
            @if $key >= $start and $key < $end {
                $result: map.merge(
                    $result,
                    (
                        $key: $value,
                    )
                );
            }
        } @else if $include-non-numeric {
            $result: map.merge(
                $result,
                (
                    $key: $value,
                )
            );
        }
    }

    @return $result;
}
